/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "sec_storage_core.h"

#include <securec.h>
#include <stddef.h>

#include "apdu_core_defines.h"
#include "apdu_utils.h"
#include "card_channel.h"
#include "logger.h"
#include "sec_storage_core_inner.h"

#define STORAGE_MAX_RETRY_NUM 1

#ifdef ENABLE_FACTORY
ResultCode StorageSetFactoryResetAuthenticationKey(const CardChannel *channel, FactoryResetLevel level,
    FactoryResetAuthAlgo algo, const StorageAuthKey *key)
{
    if (channel == NULL || key == NULL) {
        LOG_ERROR("invalid channel or key input");
        return INVALID_PARA_NULL_PTR;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_SET_RESET_KEY, (uint8_t)level, (uint8_t)algo};

    CommandApdu *cmd = CreateCommandApdu(&header, key->keyData, key->keySize, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u, level is %u, algo is %u", result, (uint32_t)level, (uint32_t)algo);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StorageSetToUserMode(const CardChannel *channel, const StorageUserModeConf *config)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }
    if (config == NULL) {
        LOG_ERROR("invalid config input");
        return INVALID_PARA_NULL_PTR;
    }

    uint8_t mode = 0;
    if (!config->pkResetable) {
        mode |= BIT_VALUE_PK_RESET_ENABLE;
    }

    if (!config->slotsResetable) {
        mode |= BIT_VALUE_SLOT_RESET_ENABLE;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_SET_TO_USER_MODE, 0, mode};
    CommandApdu *cmd = CreateCommandApdu(&header, NULL, 0, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u, config is %u", (uint32_t)result, (uint32_t)mode);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StorageSetAllSlotsSize(const CardChannel *channel, const uint16_t *slotSizeArray, uint32_t arrayLength)
{
    if (channel == NULL || slotSizeArray == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }

    if (arrayLength == 0 || arrayLength > MAX_SLOTS_NUM) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_ERR_SIZE;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_SET_ALL_SLOT_SIZES, 0, (uint8_t)arrayLength};

    uint16_t data[MAX_SLOTS_NUM] = {0};
    for (uint32_t loop = 0; loop < arrayLength; loop++) {
        uint16_t curr = slotSizeArray[loop];
        DataRevert((uint8_t *)&curr, sizeof(uint16_t));
        data[loop] = curr;
    }

    const uint8_t *apduData = (const uint8_t *)data;
    const uint32_t dataSize = arrayLength * sizeof(uint16_t);
    CommandApdu *cmd = CreateCommandApdu(&header, apduData, dataSize, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u", (uint32_t)result);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}
#endif

ResultCode StorageGetFactoryResetAuthenticationAlgo(const CardChannel *channel, FactoryResetLevel level,
    FactoryResetAuthAlgo *algo)
{
    if (channel == NULL || algo == NULL) {
        LOG_ERROR("invalid channel or algo input");
        return INVALID_PARA_NULL_PTR;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_GET_RESET_KEY, (uint8_t)level, 0};

    CommandApdu *cmd = CreateCommandApdu(&header, NULL, 0, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
        result = StorageGetFactoryResetAuthenticationAlgoFromRspApdu(rsp, algo);
        if (result != SUCCESS) {
            LOG_ERROR("StorageGetFactoryResetAuthenticationAlgoFromRspApdu failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u, level is %u, algo is %u", result, (uint32_t)level, (uint32_t)(*algo));
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StoragePrepareFactoryReset(const CardChannel *channel, uint8_t *nonce, uint32_t *length)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }
    if (nonce == NULL || length == NULL) {
        LOG_ERROR("invalid nonce or length input");
        return INVALID_PARA_NULL_PTR;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_PREPARE_RESET, 0, 0};

    CommandApdu *cmd = CreateCommandApdu(&header, NULL, 0, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
        result = StoragePrepareFactoryResetFromRspApdu(rsp, nonce, length);
        if (result != SUCCESS) {
            LOG_ERROR("StoragePrepareFactoryResetFromRspApdu failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u", (uint32_t)result);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StorageProcessFactoryReset(const CardChannel *channel, FactoryResetLevel level, const uint8_t *credential,
    uint32_t length)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_PROCESS_RESET, (uint8_t)level, 0};

    CommandApdu *cmd = CreateCommandApdu(&header, credential, length, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u", (uint32_t)result);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StorageIsSlotOperateAlgorithmSupported(const CardChannel *channel, SlotOperAlgo algo, uint32_t *available)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }

    if (available == NULL) {
        LOG_ERROR("invalid available input");
        return INVALID_PARA_NULL_PTR;
    }

    *available = 0;

    uint8_t support = 0;
    ResultCode ret = StorageGetSupportedAlgorithms(channel, NULL, &support);
    if (ret != SUCCESS) {
        return ret;
    }

    if (algo == ALGO_RAW_KEY) {
        *available = 1;
        return SUCCESS;
    }

    if (algo & support) {
        *available = 1;
    }

    return SUCCESS;
}

ResultCode StorageIsFactoryResetAlgorithmSupported(const CardChannel *channel, FactoryResetAuthAlgo algo,
    uint32_t *available)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }

    if (available == NULL) {
        LOG_ERROR("invalid available input");
        return INVALID_PARA_NULL_PTR;
    }
    *available = 0;

    uint8_t support = 0;
    ResultCode ret = StorageGetSupportedAlgorithms(channel, &support, NULL);
    if (ret != SUCCESS) {
        return ret;
    }

    if ((1 << algo) & support) {
        *available = 1;
    }

    return SUCCESS;
}

ResultCode StorageAllocateSlot(const CardChannel *channel, uint8_t slotId, const StorageSlotAttr *slotAttr,
    const StorageAuthKey *slotKey)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }
    if (slotAttr == NULL || slotKey == NULL) {
        LOG_ERROR("invalid config input");
        return INVALID_PARA_NULL_PTR;
    }

    // usually 1-byte flag, 32-byte key
    uint8_t data[MAX_SLOT_KEY_SIZE + 1] = {0};

    uint8_t flag = 0;
    if (ConvertStorageSlotAttrToBitMap(slotAttr, &flag) != SUCCESS) {
        LOG_ERROR("ConvertStorageSlotAttrToBitMap error");
        return INVALID_PARA_ERR_VALUE;
    }

    if (memcpy_s(&data[1], MAX_SLOT_KEY_SIZE, slotKey->keyData, slotKey->keySize) != EOK) {
        LOG_ERROR("slotKey size is too big = %u", slotKey->keySize);
        return INVALID_PARA_ERR_SIZE;
    }

    data[0] = flag;

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_ALLOC_SLOT, 0, slotId};
    CommandApdu *cmd = CreateCommandApdu(&header, data, slotKey->keySize + 1, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u, flag is %u", (uint32_t)result, (uint32_t)flag);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StorageGetSlotStatus(const CardChannel *channel, uint8_t slotId, StorageSlotStatus *status)
{
    if (channel == NULL || status == NULL) {
        LOG_ERROR("invalid channel or status input");
        return INVALID_PARA_NULL_PTR;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_GET_SLOT_STATUS, 0, slotId};
    CommandApdu *cmd = CreateCommandApdu(&header, NULL, 0, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
        result = StorageGetSlotStatusFromRspApdu(rsp, status);
        if (result != SUCCESS) {
            LOG_ERROR("StorageGetSlotStatusFromRspApdu failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u, slot is %u", (uint32_t)result, (uint32_t)slotId);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StorageWriteSlot(const CardChannel *channel, uint8_t slotId, const StorageAuthKey *key,
    const StorageDataArea *area, const StorageDataBuffer *data)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }

    uint8_t body[MAX_APDU_DATA_SIZE] = {0};
    uint32_t size = MAX_APDU_DATA_SIZE;

    ResultCode result = StorageWriteSlotToCmdApduData(key, area, data, body, &size);
    if (result != SUCCESS) {
        LOG_ERROR("StorageWriteSlotToCmdApduData error = %u", result);
        return result;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_WRITE_SLOT, 0, slotId};

    CommandApdu *cmd = CreateCommandApdu(&header, body, size, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u, slot is %u", (uint32_t)result, (uint32_t)slotId);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StorageReadSlot(const CardChannel *channel, uint8_t slotId, const StorageAuthKey *key,
    const StorageDataArea *area, StorageDataBuffer *data)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }

    uint8_t body[MAX_APDU_DATA_SIZE] = {0};
    uint32_t size = MAX_APDU_DATA_SIZE;

    ResultCode result = StorageReadSlotToCmdApduData(key, area, body, &size);
    if (result != SUCCESS) {
        LOG_ERROR("StorageReadSlotToCmdApduData error = %u", result);
        return result;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_READ_SLOT, 0, slotId};

    CommandApdu *cmd = CreateCommandApdu(&header, body, size, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }

        result = StorageReadSlotFromRspApdu(rsp, data);
        if (result != SUCCESS) {
            LOG_ERROR("StorageReadSlotFromRspApdu failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u, slot is %u", (uint32_t)result, (uint32_t)slotId);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StorageFreeSlot(const CardChannel *channel, uint8_t slotId, const StorageAuthKey *key)
{
    if (channel == NULL || key == NULL) {
        LOG_ERROR("invalid channel or key input");
        return INVALID_PARA_NULL_PTR;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_FREE_SLOT, 0, slotId};

    CommandApdu *cmd = CreateCommandApdu(&header, key->keyData, key->keySize, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }
    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u, slot is %u", result, slotId);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}

ResultCode StorageGetAllSlotsSize(const CardChannel *channel, uint16_t *slotSizeArray, uint32_t *arrayLength)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }

    if (slotSizeArray == NULL || arrayLength == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_GET_ALL_SLOT_SIZES, 0, 0};

    CommandApdu *cmd = CreateCommandApdu(&header, NULL, 0, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;
    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }

        result = StorageGetAllSlotsSizeFromRspApdu(rsp, slotSizeArray, arrayLength);
        if (result != SUCCESS) {
            LOG_ERROR("StorageGetAllSlotsSizeFromRspApdu failed, ret is %u", result);
            break;
        }
    } while (0);

    LOG_INFO("result is %u", (uint32_t)result);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);

    return result;
}

ResultCode StorageGetSupportedAlgorithms(const CardChannel *channel, uint8_t *facResetAlgos, uint8_t *slotOperAlgos)
{
    if (channel == NULL) {
        LOG_ERROR("invalid channel input");
        return INVALID_PARA_NULL_PTR;
    }

    ApduCommandHeader header = {CLA_DEFAULT, INS_STORAGE_GET_AUTH_ALGO, 0, 0};
    CommandApdu *cmd = CreateCommandApdu(&header, NULL, 0, MAX_APDU_RESP_SIZE);
    if (cmd == NULL) {
        LOG_ERROR("CreateCommandApdu failed");
        return CMD_APDU_CREATE_ERR;
    }

    ResultCode result = ERR_GENERIC_ERR;
    ResponseApdu *rsp = NULL;

    uint8_t reset = 0;
    uint8_t oper = 0;

    do {
        result = ChannelOpenTransmitCloseWithRetry(channel, STORAGE_MAX_RETRY_NUM, StorageResponseChecker, cmd, &rsp);
        if (result != SUCCESS) {
            LOG_ERROR("ChannelOpenTransmitCloseWithRetry failed, ret is %u", result);
            break;
        }

        result = StorageGetSupportedAlgorithmsFromRspApdu(rsp, &reset, &oper);
        if (result != SUCCESS) {
            LOG_ERROR("StorageGetSupportedAlgorithmsFromRspApdu failed, ret is %u", result);
            break;
        }

        if (facResetAlgos) {
            *facResetAlgos = reset;
        }

        if (slotOperAlgos) {
            *slotOperAlgos = oper;
        }
    } while (0);

    LOG_INFO("result is %u, resetAlgo is %u, operAlgo is %u", (uint32_t)result, (uint32_t)reset, (uint32_t)oper);
    DestroyCommandApdu(cmd);
    DestroyResponseApdu(rsp);
    return result;
}